﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Reflection;

using RexToy.Logging;
using RexToy.Xml;
using RexToy.L10N;
using RexToy.UI.WinForm.ViewModel;
using RexToy.UI.WinForm.Controls;

namespace RexToy.UI.WinForm.Pmlx
{
    public interface IPmlx
    {
        PmlxDoc PmlxDocument { get; }
        List<ControlMetaData> DynamicSizeControls { get; }
        List<ControlMetaData> DelayComputedControls { get; }
    }

    public static class PmlxExtension
    {
        private static ILog _log = LogContext.GetLogger<IPmlx>();

        public static void Render(this IPmlx pmlx, PmlxDoc doc)
        {
            doc.ThrowIfNullArgument("doc");

            doc.Initialize();

            Control c = pmlx as Control;
            Assertion.IsNotNull(c, "IPmlx instance is not a control.");

            Assertion.AreEqual(doc.RootNode.MakeType(), c.GetType(), string.Format("Root control type [{0}] mismatch with Pmlx [{1}].", c.GetType(), doc.RootNode.MakeType()));

            try
            {
                _Render(c, doc.RootNode, pmlx);
            }
            catch (PmlxException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw ex.CreateWrapException<PmlxException>();
            }
        }

        private static void _Render(Control c, PmlxNode x, IPmlx root)
        {
            foreach (var attr in x.Attributes)
            {
                if (attr.LocalName == PmlxConstant.NAME_OF_CONTROL && string.IsNullOrEmpty(attr.Prefix))
                    c.Name = attr.Value;
            }

            _log.Debug("Start render control [{0}, Name = {1}].", c.GetType(), c.Name);

            ControlMetaData meta = new ControlMetaData(c);
            //Note:Style be apply first
            ApplyStyle(c, x, meta);

            //Note:'local' attributes will overwrite the style.
            ApplyAttribute(c, x, meta);

            //Note:Add Dynamic(resizable) controls to IPmlx
            RegisterSpecialControl(root, meta);

            //Note:build child controls add them to c.Controls collection
            BuildSubControls(c, x, root);

            _log.Debug("Render control [{0}, Name = {1}] finish.", c.GetType(), c.Name);
        }

        private static void BuildSubControls(Control c, PmlxNode x, IPmlx root)
        {
            foreach (PmlxNode xChild in x.ChildNodes)
            {
                if (!xChild.Renderable)
                    continue;

                if (xChild.IsTemplate)
                {
                    ITemplateItem ti = c as ITemplateItem;
                    try
                    {
                        ti.ParseTemplate(xChild);
                    }
                    catch (Exception ex)
                    {
                        ExceptionHelper.ThrowTemplateItemWrap(ex);
                    }
                    continue;
                }

                Type typeChild = xChild.MakeType();
                if (typeChild == null)
                    ExceptionHelper.ThrowLoadFail(xChild.Prefix, xChild.LocalName);

                Control ctrlChild = (Control)Activator.CreateInstance(typeChild);
                c.Controls.Add(ctrlChild);
                _Render(ctrlChild, xChild, root);
            }
        }

        private static void RegisterSpecialControl(IPmlx pmlx, ControlMetaData meta)
        {
            if (meta.ControlResizable)
            {
                _log.Verbose("Control [{0}, Name={1}] is dynamic sized.", meta.Control.GetType(), meta.Control.Name);
                pmlx.DynamicSizeControls.Add(meta);
            }
            if (meta.DelayCompute)
            {
                _log.Verbose("Control [{0}, Name={1}] have delay compute property.", meta.Control.GetType(), meta.Control.Name);
                pmlx.DelayComputedControls.Add(meta);
            }
        }

        private static void ApplyAttribute(Control c, PmlxNode x, ControlMetaData meta)
        {
            IReflector r = Reflector.Bind(c);
            foreach (PmlxNode pAttr in x.Attributes)
            {
                //Note:Set attribute on c, ignore xmlns,style
                if (pAttr.Prefix == XAccessor.XMLNS)
                    continue;

                if (!string.IsNullOrEmpty(pAttr.Prefix))
                {
                    ExceptionHelper.ThrowAttributeHavePrefix(pAttr.Prefix, pAttr.LocalName);
                }

                if (pAttr.LocalName == PmlxConstant.STYLE || pAttr.LocalName == PmlxConstant.NAME_OF_CONTROL)
                    continue;

                //Note:W/H have special deal for %
                if (pAttr.LocalName == ControlSizeHelper.WIDTH || pAttr.LocalName == ControlSizeHelper.HEIGHT)
                {
                    ControlSizeHelper.SetSize(c, pAttr.LocalName, pAttr.Value, meta);
                    continue;
                }

                if (pAttr.LocalName.EndsWith(PmlxConstant.VM_COMMAND) && pAttr.Value.BracketedBy(StringPair.Delayed_Template_Bracket))
                {
                    IUICommand cmd = new ProxyCommand(x.OwnerDocument.ELEngine, pAttr.Value);
                    r.SetPropertyValue(pAttr.LocalName, cmd);
                    continue;
                }

                if (pAttr.Value.BracketedBy(StringPair.Delayed_Template_Bracket))
                {
                    meta.AddDelayCompute(pAttr.LocalName, pAttr.Value);
                    continue;
                }

                //Note:If text is in ${...}, it should be localize by the code logic, here only deal with 'static' content
                if (pAttr.LocalName.EndsWith(PmlxConstant.TEXT_OF_CONTROL))
                {
                    L10NHelper.SetL10nText(pAttr, r);
                    continue;
                }

                pAttr.ApplyValue(r);
            }
        }

        private static void ApplyStyle(Control c, PmlxNode x, ControlMetaData meta)
        {
            StyleCollection styles = x.OwnerDocument.Styles;
            foreach (PmlxNode pAttr in x.Attributes)
            {
                if (pAttr.LocalName == PmlxConstant.STYLE)
                {
                    Style style = styles[pAttr.Value];
                    _log.Verbose("Apply style [{0}] to control [{1}, Name={2}].", pAttr.Value, c.GetType(), c.Name);
                    style.ApplyToControl(c, meta);
                }
            }
        }

        public static T FindControl<T>(this IPmlx p, string name, bool throwOnNotFound = true) where T : Control
        {
            return FindControl(p, name, throwOnNotFound) as T;
        }

        public static Control FindControl(this IPmlx p, string name, bool throwOnNotFound = true)
        {
            Control c = null;
            Control root = (Control)p;
            Control[] controls = root.Controls.Find(name, true);
            switch (controls.Length)
            {
                case 0:
                    if (throwOnNotFound)
                        ExceptionHelper.ThrowControlNotFound(name);
                    break;

                case 1:
                    c = controls[0];
                    break;

                default:
                    ExceptionHelper.ThrowMultiControlFound(name);
                    break;
            }
            return c;
        }

        public static bool BindControlFields(this IPmlx p)
        {
            _log.Verbose("Start bind sub controls fields.");
            bool allBound = true;
            FieldInfo[] fields = p.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (var field in fields)
            {
                PmlxBoundControlAttribute boundAttribute = field.GetSingleAttribute<PmlxBoundControlAttribute>();
                if (boundAttribute != null)
                {
                    Control c;
                    if (string.IsNullOrEmpty(boundAttribute.Name))
                    {
                        c = p.FindControl(field.Name, false);
                    }
                    else
                    {
                        c = p.FindControl(boundAttribute.Name, false);
                    }

                    if (c == null)
                    {
                        allBound = false;
                        _log.Warning("Field [{0}] is not bound.", field.Name);
                    }
                    else
                    {
                        field.SetValue(p, c);
                    }
                }
            }
            _log.Verbose("Bind sub controls fields finish.");
            return allBound;
        }
    }
}